package de.rj.urwalking.ui;

import java.util.ArrayList;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.util.Log;

import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Interact2D;
import com.threed.jpct.Light;
import com.threed.jpct.Object3D;
import com.threed.jpct.RGBColor;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.World;
import com.threed.jpct.util.MemoryHelper;

import de.rj.urwalking.R;
import de.rj.urwalking.config.Constants;
import de.rj.urwalking.data.Building;
import de.rj.urwalking.data.Perspective;
import de.rj.urwalking.data.Pin;
import de.rj.urwalking.data.Poi;
import de.rj.urwalking.data.SalientObject;
import de.rj.urwalking.db.DBSalientObject;
import de.rj.urwalking.ui.fragments.dialog.IndoorObjectDialogFragment;
import de.rj.urwalking.util.CameraUtil;
import de.rj.urwalking.util.ObjHelper;
import de.rj.urwalking.util.PrefUtil;

public class IndoorRenderer extends BaseRenderer {

	private RGBColor skyColor = new RGBColor(38, 152, 211);
	private RGBColor highlightColor = new RGBColor(255, 255, 255);
	private Light[] lights = new Light[3];

	private ArrayList<Object3D> objObjects = new ArrayList<Object3D>();
	private int mFloorId;
	private Object3D previousSelectedObject;
	private ArrayList<DBSalientObject> dbSalientObjects;
	private ArrayList<SalientObject> existingLandmarks = new ArrayList<SalientObject>();
	private boolean startupDone;
	private boolean showingLandmarks;
	private GL10 oldGL;

	public IndoorRenderer(Context context) {
		super(context);
		this.context = context;

	}

	public void onSurfaceChanged(GL10 gl, int w, int h) {

		if (DEBUG) Log.d(TAG, "IndoorRenderer onSurfaceChanged");
		if (DEBUG) Log.d(TAG, "IndoorRenderer " + existingLandmarks.size() + " LMs present");

		if (oldGL != null && gl == oldGL) {
			// method was called a second time with the same context, doing nothing
			// http://www.jpct.net/forum2/index.php?topic=2547.0
			return;
		} else {
			oldGL = gl;
		}

		if (frameBuffer != null) {
			frameBuffer.dispose();
		}
		frameBuffer = new FrameBuffer(w, h);
		world = new World();

		setupCamera();
		setupLights();
		addUserArrow();

		loadScene();

		MemoryHelper.compact();
		startupDone = true;
		if (!showingLandmarks && PrefUtil.readBoolean(R.string.key_pref_show_landmarks, context, false)) {
			showLandmarks();
		}
	}

	private void loadScene() {

		int[] scene = new int[] { R.raw.cafete_detail2_obj, R.raw.cafete_detail2_mtl };

		Bundle args = new Bundle();
		args.putIntArray("scene", scene);

		ObjHelper objHelper = new ObjHelper(context, args);
		addObjects(objHelper.loadObjects());

	}

	protected void setupCamera() {

		camera = world.getCamera();
		camera.setFOVLimits(0.5f, 5f);

		if (cameraVectors != null) {
			// maybe we already have a saved position from before
			Log.d(TAG, "found cameraVectors");
			camera.setPosition(cameraVectors[0]);
			camera.setOrientation(cameraVectors[1], cameraVectors[2]);

		} else {
			// set camera to a sensible default position
			camera.setPosition(2f, -3f, 1.8f);
			camera.setOrientation(new SimpleVector(0, 1, 0), new SimpleVector(0, 0, 1));
		}

		if (cameraFov != 0.0) {
			camera.setFOV(cameraFov);
		} else {
			camera.setFOV(2.5f);
		}
	}

	private void setupLights() {
		world.setAmbientLight(50, 50, 50);

		lights[0] = new Light(world);
		lights[0].setIntensity(100, 100, 100);

		lights[1] = new Light(world);
		lights[1].setIntensity(100, 100, 100);

		lights[0].setPosition(new SimpleVector(0, 10, 10));
		lights[1].setPosition(new SimpleVector(0, -10, 10));

		lights[2] = new Light(world);
		lights[2].setIntensity(100, 100, 100);

	}

	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		if (DEBUG) Log.d(TAG, "IndoorRenderer onSurfaceCreated");
	}

	public void onDrawFrame(GL10 gl) {

		updateCamera();
		updateLights();

		frameBuffer.clear(skyColor);

		if (world.getObjects().hasMoreElements()) {

			world.renderScene(frameBuffer);
			world.draw(frameBuffer);
			frameBuffer.display();
		}

	}

	/**
	 * make one light always follow the camera
	 */

	private void updateLights() {
		SimpleVector newPos = camera.getPosition();
		lights[2].setPosition(newPos);
	}

	/**
	 * projects a ray from touch coordinates in 3d space and computes collision
	 * 
	 * @param x
	 * @param y
	 */

	public Object3D checkRayCollision(float x, float y) {

		if (frameBuffer == null || camera == null) return null;

		SimpleVector ray = Interact2D.reproject2D3DWS(camera, frameBuffer, (int) x, (int) y).normalize();

		final int maxTouchDistance = 1000;
		Object[] result = world.calcMinDistanceAndObject3D(camera.getPosition(), ray, maxTouchDistance);

		// return if we didn't hit anything
		if (result[1] == null) {
			return null;
		}

		removeHighlight();
		Object3D pickedObject3d = (Object3D) result[1];

		if (pickedObject3d.getID() == mFloorId) {
			SimpleVector newLandmarkPosition = computeIntersectionWithFloor(ray);
			SalientObject newLandmark = addNewLandmark(newLandmarkPosition);
			previousSelectedObject = newLandmark;
			return newLandmark;
		} else {
			pickedObject3d.setAdditionalColor(highlightColor);
			previousSelectedObject = pickedObject3d;

			return pickedObject3d;
		}

	}

	/**
	 * Removes the additional color an object may have if it was selected before.
	 */

	private void removeHighlight() {

		for (Object3D object : objObjects) {
			object.clearAdditionalColor();
		}

	}

	public SimpleVector[] getCameraVectors() {
		if (camera != null) {
			return new SimpleVector[] { camera.getPosition(), camera.getDirection(), camera.getUpVector() };
		}
		return null;
	}

	public void setCameraVectors(SimpleVector[] cameraVectors) {

		this.cameraVectors = cameraVectors;
	}

	public void rotateCamera(float angle) {
		CameraUtil.rotateHorizontal(angle, camera);
	}

	public void setCameraAngle(float angle) {

		if (camera != null) {
			CameraUtil.rotateHorizontalTo(angle, camera, Perspective.TYPE_3D_STREET);
		}

	}

	public void addObjects(ArrayList<Object3D> objects) {

		if (world == null) {
			return;
		}

		for (Object3D object : objects) {

			object.setCulling(false);
			object.setCollisionMode(Object3D.COLLISION_CHECK_OTHERS);
			object.strip();
			object.build();

			// need to check if this is a floor to enable different collision handling later
			if (object.getName().startsWith("boden")) {
				mFloorId = object.getID();
			}

			objObjects.add(object);
			world.addObject(object);

		}
	}

	public void emptyWorld() {
		if (world != null) {
			Log.d(TAG, "removing all objects");
			world.removeAllObjects();
			Log.d(TAG, "removed all objects");
		}
	}

	public void setSalientObjects(ArrayList<DBSalientObject> salientObjects) {

		this.dbSalientObjects = salientObjects;
		if (startupDone && !showingLandmarks && PrefUtil.readBoolean(R.string.key_pref_show_landmarks, context, false)) {
			showLandmarks();
		}
	}

	public void showLandmarks() {

		if (world == null) return;
		if (dbSalientObjects == null) return;

		for (DBSalientObject dbSalientObject : dbSalientObjects) {

			SalientObject salientObject = addNewLandmark(new SimpleVector(dbSalientObject.getX(),
					dbSalientObject.getY(), 0));
			salientObject.addName(dbSalientObject.getName(), dbSalientObject.getId());
			existingLandmarks.add(salientObject);
		}

		showingLandmarks = true;
	}

	public void hideLandmarks() {
		for (SalientObject lm : existingLandmarks) {
			// if (world.getObject(lm.getID()) != null) {
			// world.removeObject(lm);
			// }
		}
	}

}
